/** * Copyright 2010 The PlayN Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package playn.html; import java.util.Map; import java.util.HashMap; import com.google.gwt.canvas.dom.client.CanvasGradient; import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.dom.client.CanvasElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.user.client.Window; import pythagoras.f.Point; import playn.core.CanvasImage; import playn.core.Font; import playn.core.Gradient; import playn.core.Graphics; import playn.core.TextFormat; import playn.core.TextLayout; import playn.core.TextWrap; import playn.core.gl.GL20; import playn.core.gl.Scale; public abstract class HtmlGraphics implements Graphics { static String cssColorString(int color) { double a = ((color >> 24) & 0xff) / 255.0; int r = (color >> 16) & 0xff; int g = (color >> 8) & 0xff; int b = (color >> 0) & 0xff; return "rgba(" + r + "," + g + "," + b + "," + a + ")"; } private static native void setLoadHandlers(ImageElement img, EventHandler onload, EventHandler onerror) /*-{ img.onload = function(e) { onload.@playn.html.EventHandler::handleEvent(Lcom/google/gwt/dom/client/NativeEvent;)(e); }; img.onerror = function(e) { onerror.@playn.html.EventHandler::handleEvent(Lcom/google/gwt/dom/client/NativeEvent;)(e); }; }-*/; protected final CanvasElement dummyCanvas; protected Element rootElement; private final Context2d dummyCtx; private final Point mousePoint = new Point(); private final Element measureElement; private final Map<Font,HtmlFontMetrics> fontMetrics = new HashMap<Font,HtmlFontMetrics>(); private static final String HEIGHT_TEXT = "THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog_-+!.,[]0123456789"; private static final String EMWIDTH_TEXT = "m"; // Temporary hack to fix mouse coordinates for scaled fullscreen mode. static float experimentalScale = 1; /** * Sizes or resizes the root element that contains the PlayN view. * @param width the new width, in pixels, of the view. * @param height the new height, in pixels, of the view. */ public void setSize(int width, int height) { rootElement.getStyle().setWidth(width, Unit.PX); rootElement.getStyle().setHeight(height, Unit.PX); } /** * Registers metrics for the specified font in the specified style and size. This overrides the * default font metrics calculation (which is hacky and inaccurate). If you want to ensure * somewhat consistent font layout across browsers, you should register font metrics for every * combination of font, style and size that you use in your app. * * @param lineHeight the height of a line of text in the specified font (in pixels). */ public void registerFontMetrics(String name, Font.Style style, float size, float lineHeight) { HtmlFont font = new HtmlFont(this, name, style, size); HtmlFontMetrics metrics = getFontMetrics(font); // get emwidth via default measurement fontMetrics.put(font, new HtmlFontMetrics(font, lineHeight, metrics.emwidth)); } @Override public CanvasImage createImage(float width, float height) { return new HtmlCanvasImage(ctx(), scale(), HtmlCanvas.create(scale(), width, height)); } @Override public Gradient createLinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions) { assert colors.length == positions.length; CanvasGradient gradient = dummyCtx.createLinearGradient(x0, y0, x1, y1); for (int i = 0; i < colors.length; ++i) { gradient.addColorStop(positions[i], cssColorString(colors[i])); } return new HtmlGradient(gradient); } @Override public Gradient createRadialGradient(float x, float y, float r, int[] colors, float[] positions) { assert colors.length == positions.length; CanvasGradient gradient = dummyCtx.createRadialGradient(x, y, 0, x, y, r); for (int i = 0; i < colors.length; ++i) { gradient.addColorStop(positions[i], cssColorString(colors[i])); } return new HtmlGradient(gradient); } @Override public Font createFont(String name, Font.Style style, float size) { return new HtmlFont(this, name, style, size); } @Override public TextLayout layoutText(String text, TextFormat format) { return HtmlTextLayout.layoutText(this, dummyCtx, text, format); } @Override public TextLayout[] layoutText(String text, TextFormat format, TextWrap wrap) { return HtmlTextLayout.layoutText(this, dummyCtx, text, format, wrap); } @Override public int screenHeight() { return Document.get().getDocumentElement().getClientHeight(); } @Override public int screenWidth() { return Document.get().getDocumentElement().getClientWidth(); } @Override public float scaleFactor() { return scale().factor; } @Override public GL20 gl20() { throw new UnsupportedOperationException(); } @Override public HtmlGLContext ctx() { return null; } protected HtmlGraphics(HtmlPlatform.Config config) { Document doc = Document.get(); dummyCanvas = doc.createCanvasElement(); dummyCtx = dummyCanvas.getContext2d(); rootElement = doc.getElementById(config.rootId); if (rootElement == null) { rootElement = doc.createDivElement(); rootElement.setAttribute("style", "width: 640px; height: 480px"); doc.getBody().appendChild(rootElement); } else { // clear the contents of the root element, if present rootElement.setInnerHTML(""); } // create a hidden element used to measure font heights measureElement = doc.createDivElement(); measureElement.getStyle().setVisibility(Style.Visibility.HIDDEN); measureElement.getStyle().setPosition(Style.Position.ABSOLUTE); measureElement.getStyle().setTop(-500, Unit.PX); measureElement.getStyle().setOverflow(Style.Overflow.VISIBLE); measureElement.getStyle().setWhiteSpace(Style.WhiteSpace.NOWRAP); rootElement.appendChild(measureElement); if (config.experimentalFullscreen) { Window.addResizeHandler(new ResizeHandler() { @Override public void onResize(ResizeEvent event) { if (fullScreenWidth() == event.getWidth() && fullScreenHeight() == event.getHeight()) { experimentalScale = Math.min((float) fullScreenWidth() / (float) width(), (float) fullScreenHeight() / (float) height()); // less distance to the top int yOfs = (int) ((fullScreenHeight() - height() * experimentalScale) / 3.f); int xOfs = (int) ((fullScreenWidth() - width() * experimentalScale) / 2.f); rootElement().setAttribute( "style", "width:" + experimentalScale * width() + "px; " + "height:" + experimentalScale*height() + "px; " + "position:absolute; left:" + xOfs + "px; top:" + yOfs); // This is needed to work around a focus bug in Chrome :( Window.alert("Switching to fullscreen mode."); Document.get().getBody().addClassName("fullscreen"); } else { experimentalScale = 1; rootElement().removeAttribute("style"); Document.get().getBody().removeClassName("fullscreen"); } }}); } } abstract Scale scale(); HtmlFontMetrics getFontMetrics(HtmlFont font) { HtmlFontMetrics metrics = fontMetrics.get(font); if (metrics == null) { // TODO: when Context2d.measureText some day returns a height, nix this hackery measureElement.getStyle().setFontSize(font.size(), Unit.PX); measureElement.getStyle().setFontWeight(Style.FontWeight.NORMAL); measureElement.getStyle().setFontStyle(Style.FontStyle.NORMAL); measureElement.getStyle().setProperty("fontFamily", font.name()); measureElement.setInnerText(HEIGHT_TEXT); switch (font.style()) { case BOLD: measureElement.getStyle().setFontWeight(Style.FontWeight.BOLD); break; case ITALIC: measureElement.getStyle().setFontStyle(Style.FontStyle.ITALIC); break; case BOLD_ITALIC: measureElement.getStyle().setFontWeight(Style.FontWeight.BOLD); measureElement.getStyle().setFontStyle(Style.FontStyle.ITALIC); break; default: break; // nada } float height = measureElement.getOffsetHeight(); measureElement.setInnerText(EMWIDTH_TEXT); float emwidth = measureElement.getOffsetWidth(); metrics = new HtmlFontMetrics(font, height, emwidth); fontMetrics.put(font, metrics); } return metrics; } Point transformMouse(float x, float y) { return mousePoint.set(x / scale().factor, y / scale().factor); } abstract Element rootElement(); abstract void paint(); private native int fullScreenWidth() /*-{ return $wnd.screen.width; }-*/; private native int fullScreenHeight() /*-{ return $wnd.screen.height; }-*/;}